summaryrefslogblamecommitdiffstats
path: root/d.c
blob: eefdece38bcb5f57d9cf0c75f1f8021c3eeb0437 (plain) (tree)










































































































































































                                                                                                                                                                                      
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <string.h>
#include <sys/types.h>
#include <error.h>
#include <endian.h>
#include <inttypes.h>
#include <curl/curl.h>
#include <cjson/cJSON.h>
#define S0(x) (x ? x : "")
enum entry {
	TALLY,
	QUOTA
};
#define TYPE_SHIFT (8*7)
#define STATION_SHIFT (8*6)
#define VALUE_SHIFT (8*5)
#define TYPE_INS(x) ((uint64_t) x << TYPE_SHIFT)
#define STATION_INS(x) ((uint64_t) x << STATION_SHIFT)
#define VALUE_INS(x) ((uint64_t) x << VALUE_SHIFT)
#define TYPE_RET(x) ((x & TYPE_INS(0xFF)) >> TYPE_SHIFT)
#define STATION_RET(x) ((x & STATION_INS(0xFF)) >> STATION_SHIFT)
#define VALUE_RET(x) ((x & VALUE_INS(0xFF)) >> VALUE_SHIFT)
#define TIME 0xFFFFFFFF
char * response = NULL;
size_t response_len = 0;
size_t write_callback (char * ptr, size_t size, size_t nmemb, void * userdata __attribute__((unused))) {
	nmemb *= size;
	char * mem = realloc(response, response_len+nmemb+1);
	if (!mem)
		return 0;
	response = mem;
	strncpy(response+response_len, ptr, nmemb);
	response[(response_len += nmemb)] = '\0';
	return nmemb;
}
int main (int argc, char ** argv) {
	cJSON * json = NULL;
	int r = 0;
	CURL * curl = NULL;
	unsigned char tally[256];
	unsigned char quota[256];
	memset(tally, 255, 256);
	memset(quota, 255, 256);
	if (fseek(stdin, -8, SEEK_END) == -1) {
		if (errno == EINVAL)
			fprintf(stderr, "nova (prazna) podatkovna zbirka!\n");
		else
			error_at_line(1, errno, __FILE__, __LINE__, "fseek. uporaba: %s >> db < db", S0(argv[0]));
	}
	uint64_t entry;
	while (fread(&entry, sizeof entry, 1,  stdin)) {
		entry = be64toh(entry);
		char čas[256];
		time_t time = entry & TIME;
		strftime(čas, 256, "%c", localtime(&time));
		switch (TYPE_RET(entry)) {
			case TALLY:
				if (tally[STATION_RET(entry)] == 255) {
					tally[STATION_RET(entry)] = VALUE_RET(entry);
					fprintf(stderr, "[db] %s: na postaji %" PRIu64 " je %" PRIu64 " koles\n", čas, STATION_RET(entry), VALUE_RET(entry));
				}
				break;
			case QUOTA:
				if (quota[STATION_RET(entry)] == 255) {
					quota[STATION_RET(entry)] = VALUE_RET(entry);
					fprintf(stderr, "[db] %s: na postaji %" PRIu64 " je %" PRIu64 " postajališč\n", čas, STATION_RET(entry), VALUE_RET(entry));
				}
				break;
			default:
				error_at_line(2, 0, __FILE__, __LINE__, "invalid entry with type %" PRIu64, TYPE_RET(entry));
		}
		if (fseek(stdin, -16, SEEK_CUR) == -1)
			break;
	}
	char curlerr[CURL_ERROR_SIZE];
	curl = curl_easy_init();
	if (!curl)
		error_at_line(2, 0, __FILE__, __LINE__, "!curl");
	curl_easy_setopt(curl, CURLOPT_URL, "https://api.jcdecaux.com/vls/v3/stations?apiKey=frifk0jbxfefqqniqez09tw4jvk37wyf823b5j1i&contract=ljubljana");
	curl_easy_setopt(curl,  CURLOPT_ERRORBUFFER, curlerr);
	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
	char buf[256];
	snprintf(buf, 255, "bicikelj-stat/0.0.0 sends at most one request per 50 seconds. contact by email: %s", argc > 1 ? argv[1] : "not provided");
	curl_easy_setopt(curl, CURLOPT_USERAGENT, buf);
	while (1) {
		fprintf(stderr, ".");
		curlerr[0] = '\0';
		response_len = 0;
		CURLcode res = curl_easy_perform(curl);
		if (res != CURLE_OK) {
			error_at_line(0, 0, __FILE__, __LINE__, "!= CURLE_OK: %s", curlerr[0] ? curlerr : curl_easy_strerror(res));
			r = 3;
			goto r;
		}
		json = cJSON_Parse(response);
		cJSON * station = NULL;
		cJSON_ArrayForEach(station, json) {
			cJSON * number = cJSON_GetObjectItem(station, "number");
			cJSON * name = cJSON_GetObjectItem(station, "name");
			cJSON * capacity = cJSON_GetObjectItem(cJSON_GetObjectItem(station, "totalStands"), "capacity");
			cJSON * bikes = cJSON_GetObjectItem(cJSON_GetObjectItem(cJSON_GetObjectItem(station, "totalStands"), "availabilities"), "bikes");
			if (!cJSON_IsNumber(number)) {
				error_at_line(0, 0, __FILE__, __LINE__, "number");
				r = 4;
				goto r;
			}
			if (!cJSON_IsNumber(capacity)) {
				error_at_line(0, 0, __FILE__, __LINE__, "capacity");
				r = 5;
				goto r;
			}
			if (!cJSON_IsNumber(bikes)) {
				error_at_line(0, 0, __FILE__, __LINE__, "bikes");
				r = 6;
				goto r;
			}
			if (!cJSON_IsString(name)) {
				error_at_line(0, 0, __FILE__, __LINE__, "name");
				r = 7;
				goto r;
			}
			if (number->valueint >= 255 || capacity->valueint >= 255 || bikes->valueint >= 255) {
				error_at_line(0, 0, __FILE__, __LINE__, "number || capacity || bikes >= 255: evil server!");
				r = 8;
				goto r;
			}

			char čas[256];
			time_t tajm = entry = time(NULL);
			strftime(čas, 256, "%c", localtime(&tajm));
			if (quota[number->valueint] != capacity->valueint) {
				unsigned old = quota[number->valueint];
				entry |= TYPE_INS(QUOTA) | STATION_INS(number->valueint) | VALUE_INS(capacity->valueint);
				if (old == 255)
					fprintf(stderr, "%s: na postaji %s (%d) je %d postajališč\n", čas, name->valuestring, number->valueint, capacity->valueint);
				else
					fprintf(stderr, "%s: na postaji %s (%d) je %d postajališč (prej %u)\n", čas, name->valuestring, number->valueint, capacity->valueint, old);
				quota[number->valueint] = capacity->valueint;
				entry = htobe64(entry);
				fwrite(&entry, sizeof entry, 1, stdout);
			}
			entry = time(NULL);
			if (tally[number->valueint] != bikes->valueint) {
				unsigned old = tally[number->valueint];
				entry |= TYPE_INS(TALLY) | STATION_INS(number->valueint) | VALUE_INS(bikes->valueint);
				if (old == 255)
					fprintf(stderr, "%s: na postaji %s (%d) je %d koles\n", čas, name->valuestring, number->valueint, bikes->valueint);
				else
					fprintf(stderr, "%s: na postaji %s (%d) je %d koles (prej %u)\n", čas, name->valuestring, number->valueint, bikes->valueint, old);
				tally[number->valueint] = bikes->valueint;
				entry = htobe64(entry);
				fwrite(&entry, sizeof entry, 1, stdout);
			}
			fflush(stdout);
		}
		cJSON_Delete(json);
		json = NULL;
		sleep(50);
	}
r:
	cJSON_Delete(json);
	json = NULL;
	curl_easy_cleanup(curl);
	curl = NULL;
	free(response);
	return r;
}